﻿import tkinter as tk, tkinter.ttk as ttk, tkinter.messagebox, tkinter.scrolledtext, tkinter.simpledialog,tkinter.filedialog, socket,datetime,os,stat
import ui_data,channel
import protocol

class RAW(protocol.PData):
    def __init__(self, **kwargs) -> None:
        super().__init__()
        self.type = self.__class__.__name__
        self.info = "无"
        self.ml = kwargs["ml"] if "ml" in kwargs else None
        self.mf = kwargs["mf"] if "mf" in kwargs else None
        self.ms = kwargs["ms"] if "ms" in kwargs else None
    @staticmethod
    def GetClassName():
        """静态方法 获取 此 class 名 """
        return __class__.__name__

    @staticmethod
    def GUI(parent, raw, **kwargs):
        """配置界面"""
        lf = tk.LabelFrame(parent, name="protocol", text="规约设置", labelanchor="nw",height=100)
        tk.Label(lf, text="无").grid()
        return lf
        pass
    
    def apply(self):
        pass
        
    def validate(self):
        """检查参数的有效性"""
        return True
        pass
    
    def set(self, **kwargs):
        if "ml" in kwargs: self.ml = kwargs["ml"] 
        if "mf" in kwargs: self.mf = kwargs["mf"]
        if "ms" in kwargs: self.ms = kwargs["ms"] 
 
    def UI(self, *args):
        """mainframe 窗口界面"""
        return UI_RAW(*args)

class UI_RAW(tk.PanedWindow):
    SEND_ID = 1 # 行号， 只加一
    RECV_ID = 1
    STATE = {} # 当调用stop函数时暂时保存 state，等待再次调用 restart
    """RAW 窗口 ，继承于 tk.PanedWindow 左边是一个 ttk.TreeView, 右边又是一个 tk.PanedWindow 窗口 ，分为上下窗口分别为发送和接收窗口"""
    def __init__(self:tk.PanedWindow, parent:tk.Frame, UI:ui_data.UI_data , data:ui_data.AData ) -> None:
        self.UI:ui_data.UI_data  = UI
        self.data:ui_data.AData = data
        super().__init__(parent, orient="vertical" , name="mainframe" + str(data.channel.id))
        pSendArea = tk.LabelFrame(self, name= "sendarea", text = "发送区：", background = "#668B8B")
        pRecvArea = tk.LabelFrame(self, name= "recvarea", text = "接收区：", background = "#636363")
        self.add(pSendArea)
        self.add(pRecvArea)
        self.paneconfigure(pSendArea, height = parent.winfo_height()//2)
        # self.paneconfigure(pSendArea,{"height": parent.winfo_height()//2})
        #发送部分
        pSendCtrl = tk.Frame(pSendArea)
        pSendAdd = tk.Button(pSendCtrl, text="添加报文", command = self.on_button_SendAdd_click)
        pSendAdd.grid(column=0, row=0)
        tk.Button(pSendCtrl, text="全部清除", command = self.on_button_SendClear_click).grid(column=1, row=0)
        tk.Button(pSendCtrl, text="载入报文", command = self.on_button_SendLoad_click).grid(column=2, row=0)
        tk.Button(pSendCtrl, text="保存报文", command = self.on_button_SendSave_click, name="nodisabled").grid(column=3, row=0)
        tk.Button(pSendCtrl, text="发送文件", command = self.on_button_SendFile_click).grid(column=4, row=0)
        self.send_count = tk.IntVar(self, value=0) #发送计数 单位 Bytes
        self.send_file = None #正在发送的文件
        se = tk.Entry(pSendCtrl,name="send_count", textvariable= self.send_count, state="readonly",width=10 )
        se.grid(column=10, row = 0)
        se.bind("<Button-3>", func=self.on_SendRecvCount_rclick)
        pSendCtrl.pack(fill="x")
        scrollbarX = tk.Scrollbar(pSendArea, orient="horizontal")
        scrollbarX.pack(fill="x", side="bottom", anchor="s")
        scrollbarY = tk.Scrollbar(pSendArea, orient="vertical" )
        scrollbarY.pack(fill="y",side="right", anchor="e")
        pSendText = ttk.Treeview(pSendArea,name="sendarea",
                                    selectmode = "extended",
                                    xscrollcommand=scrollbarX.set,
                                    yscrollcommand=scrollbarY.set,
                                    columns=["Send", "Params","Text" ])
        pSendText.column("#0", width=30, minwidth=30,anchor="e", stretch =False)
        pSendText.column("Send", width=50, minwidth=30, anchor="center", stretch =False)
        pSendText.column("Params", width=80, minwidth=30, anchor="center", stretch =False)
        pSendText.column("Text", width= 40, anchor="w")
        pSendText.heading("#0", text= "No")
        pSendText.heading("Send", text= "发送")
        pSendText.heading("Params", text= "参数")
        pSendText.heading("Text", text= "报文")
        pSendText.pack(fill="both", expand= 1 )
        pSendText.bind("<Double-1>", func = self.on_treeview_pSendText_dbclick)
        pSendText.bind("<ButtonRelease-1>", func = self.on_treeview_pSendText_click)
        pSendText.bind("<Button-3>", func = self.on_treeview_pSendText_contextmenu)
        self.pSendText:ttk.Treeview = pSendText
        self.send_params:dict = {} # 结构 [ bool :是否以HEX发送, bool:是否间隔发送, int:间隔发送时间,bool 是否在一直发送 , after_ID | None正在发送的ID]
        #接收部分
        pRecvCtrl = tk.Frame(pRecvArea)
        self.recv_data = [  tk.BooleanVar(self, value= False),  # 使能显示为HEX格式
                            tk.IntVar(self,value=0),  # 接收字节数
                            tk.BooleanVar(self, value=0),  # 使能直接接到到文件
                            tk.StringVar(self, value=""), # 直接接到到文件的文件名
                            tk.BooleanVar(self, value=1), # 使能自动滚动到最下面
                            tk.BooleanVar(self, value=1)] # 使能 也显示发送的报文
        tk.Checkbutton(pRecvCtrl, text="显示Hex格式",variable= self.recv_data[0]).grid(column = 0, row = 0 )
        tk.Button(pRecvCtrl, text="清空", command= self.on_RecvClean_click).grid(column=1,row=0 )
        tk.Button(pRecvCtrl, text="保存", command= self.on_RecvSave_click, name="nodisabled" ).grid(column=2,row=0 )
        tk.Label(pRecvCtrl, text="接收字节数:").grid(column=3,row=0)
        re = tk.Entry(pRecvCtrl,name="recv_count", textvariable= self.recv_data[1], state="readonly", width= 10)
        re.grid(column=4,row=0)
        re.bind("<Button-3>", func=self.on_SendRecvCount_rclick)
        tk.Checkbutton(pRecvCtrl, text="显示发送报文",variable= self.recv_data[5]).grid(column = 6, row = 0 )
        tk.Checkbutton(pRecvCtrl, text="自动滚动",variable= self.recv_data[4]).grid(column = 7, row = 0 )
        recv_to_file = tk.Checkbutton(pRecvCtrl, 
                                    text="接收到文件", 
                                    variable= self.recv_data[2],
                                    onvalue=True, offvalue=False, 
                                    command=self.on_RecvToFileEnable)
        recv_to_file.grid(column=8,row=0 )
        # recv_to_file.bind("<ButtunRelease-1>", func=self.on_RecvToFileEnable)
        tk.Entry(pRecvCtrl, textvariable= self.recv_data[3], width= 50, state="readonly" ).grid(column=9,row=0 )
        
        pRecvCtrl.pack(fill="x")
        pRecvText = tkinter.scrolledtext.ScrolledText(pRecvArea, state="disabled")
        pRecvText.pack(fill="both", expand=1)
        self.pRecvText:tkinter.scrolledtext.ScrolledText = pRecvText
        self.stop = self.disable
        pass
    

    
    def on_SendRecvCount_rclick(self, event):
        """右击清除计数"""
        m = tk.Menu(event.widget)
        m.add_command(label = "清空计数",command = lambda: self.on_ClearCount_command(event.widget))
        m.tk_popup(event.x_root , event.y_root)
    
    def on_ClearCount_command(self, widget):
        """清空计数"""
        if(widget._name == "send_count"):
            self.send_count.set(0) 
        elif(widget._name == "recv_count"):
            self.recv_data[1].set(0)
            
    def on_RecvClean_click(self):
        self.pRecvText.configure(state="normal")
        self.pRecvText.delete("1.0", "end")
        self.pRecvText.configure(state="disabled")
        self.RECV_ID = 1
    
    def on_RecvSave_click(self):
        """保存窗口的报文"""
        d = Dialog_RecvTextSave_Param(self)
        if(d.data == None): return
        f =  open(d.data[4], mode="a+" )
        line_count = self.pRecvText.count("1.0", "end", "lines")
        for i in range(1, line_count[0]+1,1):
            t = ""
            i_str = str(i )
            if(d.data[0]):
                t += self.pRecvText.get(i_str + ".0", i_str + ".6")
            if(d.data[1]):
                t += self.pRecvText.get(i_str + ".6", i_str + ".33")
            if(d.data[2]):
                t += self.pRecvText.get(i_str + ".33", i_str + ".42")
            if(d.data[3]):
                t += self.pRecvText.get(i_str + ".42", i_str + ".end") 
            t += "\n"
            try:
                f.write(t)
            except Exception as e:
                print (e)
                pass
            pass
        f.close()
        pass
    
    def on_RecvToFileEnable (self):
        """"直接保存到文件, 只保存原始的报文到文件"""
        if(self.recv_data[2].get()):

            f = tkinter.filedialog.asksaveasfile(defaultextension = ".log",
                                                    initialfile="recv.log",
                                                    initialdir= self.recv_data[3].get(),
                                                    mode="ab",
                                                    filetypes=[("log file", ".log"), ("text files",".txt"), ("all files", ".*")])
            if (f == None):
                self.recv_data[2].set(False)
                return
            self.recv_data[3].set(f.name)
            self.recv_data.append(f)
        else:
            self.recv_data[6].close()
            self.recv_data.pop()
        pass
    
    def update_recv_UI(self, buf:bytes, addr = None):
        """收到报文， 更新窗口"""
        recv_len = len(buf)
        self.recv_data[1].set(self.recv_data[1].get() + recv_len)
        # 如果使能的直接保存接收报文那就直接保存
        if(self.recv_data[2].get() and self.recv_data[6] and self.recv_data[6].writable):
            self.recv_data[6].write(buf)
            self.show_msg("保存到文件", recv_len, "RCVF")
        else:
            if(self.recv_data[0].get()):
                buf = buf.hex( " ")
            if(self.data.channel.type == "UDP"):
                self.show_msg(buf, recv_len, addr[0] + ":"+ str(addr[1]))
            else:
                self.show_msg(buf, recv_len, "RECV")
        pass
    
    def show_msg(self, buf:str, buf_len:int, type:str):
        if(isinstance(buf, bytes)):
            try:
                buf = buf.decode("utf-8" )
            except Exception as e:
                buf = buf.__str__()
        self.pRecvText.configure(state="normal")
        self.pRecvText.insert(tk.END,"%8d %s LEN:%8d %s:%s\n" %(self.RECV_ID, 
                                                                    datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
                                                                    buf_len, type, buf))
        ID_str = self.RECV_ID.__str__()
        tags = (("NO_" + ID_str, ID_str+".0", ID_str + ".8", "#FF0000"), 
                ("T_"+ID_str, ID_str+".9", ID_str + ".35", "#00FF00"), 
                ("L_"+ID_str, ID_str+".40", ID_str + ".48", "#0000FF"), 
                ("M_"+ID_str, ID_str+"."+ str(50+ len(type)), ID_str+".end", "#00FFFF"))
        for t in tags:
            self.pRecvText.tag_add(t[0], t[1], t[2])
            self.pRecvText.tag_configure(t[0], background=t[3])
        self.pRecvText.configure(state="disabled")
        if(self.recv_data[4].get()): #自动滚动到最下面
            self.pRecvText.see(tk.END)
        self.RECV_ID += 1
        
    def send_interval(self,  buf:bytes, send_params_item:dict):
        """发送窗口间隔发送报文"""
        if self.data.channel.type == "UDP":
            len = self.data.channel.send(buf, tuple(send_params_item["rip"][1]) ) 
        else:
            len = self.data.channel.send(buf) 
        if(not isinstance(len,int)):
            self.UI.shutdown(self.data)
            tkinter.messagebox.showerror( title = "ERROR", message= "通道" + str(self.data.channel.id) + "发送报文出错：" + 
                                                                    self.data.channel.info + " / " + self.data.protocol.info +
                                                                    "\n"+ len.__class__.__name__  + " " + str(len))
            return
        if(self.recv_data[5].get()):
            if(self.data.channel.type == "UDP"):
                self.show_msg(buf, len, send_params_item["rip"][1][0] +":" + str(send_params_item["rip"][1][1]))
            else:
                self.show_msg(buf, len, "SEND")
        self.send_count.set(self.send_count.get()+len)
        if(send_params_item["interval"][4] ):
            send_params_item["interval"][5] = self.pSendText.after(send_params_item["interval"][1][1],func=lambda:self.send_interval(buf, send_params_item))
        else:
            send_params_item["interval"][5] = None
    
    def __get_SendAdd_params_text(self, send_params_item:dict):
        #更新 对应的 参数字符串
        t = ""
        for k,v in send_params_item.items():
            if k == "msg":
                continue
            if v[0]== "bool":
                v[3] = v[1][1] if v[1][0] else v[1][2]
            elif v[0]== "bool_int": 
                v[3] = v[1][2] +  str(v[1][1]) +  v[1][3] if v[1][0] else ""
            elif v[0] == "str":
                v[3] = v[1][1] + v[1][0] + v[1][2]
            elif v[0] == "list":
                v[3] = v[1][1] + v[1][0][3] + v[1][2]
            elif v[0] == "str_int":
                v[3] = v[1][0] + ":" + str (v[1][1])
            if(v[3]):
                t += v[3]
                t +=","
        return t.rstrip(",")

    def on_button_SendAdd_click(self , send_params_item = None):
        """发送窗口添加报文"""
        
        # 以下是默认参数
        the_defalut_dict = {
            "hex":["bool", [False, "HEX", ""], "以HEX格式解析发送", None],
            "interval":["bool_int", [False, 2000, "", "ms"], "发送间隔", None, False, None],  
            # "str_example":["str", [ "","prefix", "suffix"], "此处显示的配置窗口", "此处显示在发送窗口"],
            # "list_example":["list", [ [], "prefix", "suffix", id], "此处显示的配置窗口", "此处显示在发送窗口"],
            # "str_int_example":["str_int", ["127.0.0.1",2404,], "远方IP", "此处显示在发送窗口"],
            "msg": "54 65 73 74 20 4D 65 73 73 61 67 65"
        }
        if send_params_item:
            send_params_default_item = send_params_item
        else:
            send_params_default_item = the_defalut_dict
            if hasattr(self.data.channel, "send_params"):
                send_params_default_item.update(self.data.channel.send_params)
        
        #更新 对应的 参数字符串
        t = self.__get_SendAdd_params_text(send_params_default_item)
        row = self.pSendText.insert("", index=self.SEND_ID, text = self.SEND_ID, values= ["发送", t, send_params_default_item["msg"],] )

        self.send_params[self.SEND_ID] = send_params_default_item
        self.pSendText.see(row)
        self.SEND_ID += 1
        pass
    
    def on_button_SendClear_click(self):
        """清除所有发送窗口报文"""
        for i,s in self.send_params.items():
            if(s["interval"][4]):
                self.pSendText.after_cancel(s["interval"][5])
                s["interval"][4] = False
                s["interval"][5] = None
        self.send_params.clear()

        for i in self.pSendText.get_children():
            self.pSendText.delete(i)
        self.SEND_ID = 0

    def on_button_SendLoad_click(self):
        """从文件载入报文设置"""
        f = tkinter.filedialog.askopenfile(defaultextension = ".conf",
                                                initialfile="send.conf",
                                                initialdir= os.getcwd(),
                                                filetypes=[("conf file", ".conf"), ("text files",".txt"), ("all files", ".*")])
        if(f == None): return
        
        while True:
            try:
                t:str = f.readline()
                if(t):
                    s = eval(t)
                    self.on_button_SendAdd_click(s)
                    self.send_params[self.SEND_ID] = s
                    self.SEND_ID+=1
                else:
                    break
            except Exception as e:
                tkinter.messagebox.showerror(title="无法载入配置文件:", message="错误：" + e.__str__())
                break
            pass
        f.close()
        pass
        
    def on_button_SendSave_click(self):
        """将报文设置保存到文件"""
        f = tkinter.filedialog.asksaveasfile(defaultextension = ".conf",
                                                initialfile="send.conf",
                                                initialdir= os.getcwd(),
                                                filetypes=[("conf file", ".conf"), ("text files",".txt"), ("all files", ".*")])
        if(f == None): return
        
        for k,v in self.send_params.items():
            t = v.__str__()
            f.write(t)
            f.write("\n")
        f.close()
        pass
        
    def on_cmd_SendDel_click(self, row):
        """删除某一行报文"""
        if(self.pSendText.exists(row)):
            t = self.pSendText.item(row, "text")
            id = int(self.pSendText.item(row, "text"))
            if(id in self.send_params):
                if(self.send_params[id][4]):
                    self.pSendText.after_cancel(self.send_params[id][4])
                    self.send_params[id][4] = None
                    self.send_params[id][3] = False
                self.send_params.pop(id)
            self.pSendText.delete(row)
        if(not self.pSendText.get_children()):
            self.SEND_ID = 0
    
    def on_button_SendFile_click(self):
        """发送一个文件"""
        if self.send_file: #正在发送文件需要关闭
            self.send_file.close()
        self.send_file = tkinter.filedialog.askopenfile(parent=self,
                                            mode="rb",
                                            defaultextension = ".txt",
                                            initialfile="send.txt",
                                            initialdir= os.getcwd(),
                                            filetypes=[("text files",".txt"), ("all files", ".*")])
        if self.send_file== None: return                                  
        try:
            send_len = self.data.channel.fd.sendfile(self.send_file)
        except Exception as e:
            self.UI.shutdown(self.data)
            tkinter.messagebox.showerror(title="发送文件失败:", message="错误：" + e)
            return
        self.send_count.set(self.send_count.get() + send_len)
        m = "发送文件成功"+self.send_file.name
        self.show_msg(m, send_len, "SNDF")
        self.send_file.close()
        self.send_file = None
        pass
    
    def on_treeview_pSendText_dbclick(self, event):
        """报文参数修改"""
        # print("on_pSendText_dbclick", event, event.widget)
        column = self.pSendText.identify_column(event.x)
        row = self.pSendText.identify_row(event.y)
        if(row):
            id = self.pSendText.item(row, "text")
            if(column == "#2"): #修改参数
                if( id in self.send_params):
                    d = Dialog_Send_Param(self.pSendText, self.send_params[id])
                    if(d.applied):
                        t = self.__get_SendAdd_params_text(d.params)
                        self.pSendText.set(row, column, t)
            elif(column == "#3"): # 修改报文
                d = Dialog_SendText_Param(self.pSendText, self.send_params[id])
                if(d.applied):
                    self.pSendText.set(row,"#3", self.send_params[id]["msg"])
                ...
        pass
    
    def on_treeview_pSendText_click(self, event):
        """发送报文"""
        # print("on_pSendText_dbclick", event, event.widget)
        column = self.pSendText.identify_column(event.x)
        row = self.pSendText.identify_row(event.y)
        if(row and column == "#1"):
            item = self.pSendText.item(row)
            id = self.pSendText.item(row,"text")
            if(id in self.send_params):
                ### 检查 HEX 格式 间隔发送等
                if(self.send_params[id]["hex"][1][0]): # HEX 格式解析
                    buf:bytes = bytes.fromhex(self.send_params[id]["msg"])
                else:
                    buf:bytes = self.send_params[id]["msg"].encode("utf-8")
                if(not buf):
                    return
                if(self.send_params[id]["interval"][1][0]): # 间隔发送功能
                    if(self.send_params[id]["interval"][4]): # 正在间隔发送
                        self.send_params[id]["interval"][4] = False
                        self.pSendText.set(row, "#1", "发送")
                        if(self.send_params[id]["interval"][5]):
                            self.pSendText.after_cancel(self.send_params[id]["interval"][5])
                            self.send_params[id]["interval"][5] = None
                    else: # 开始间隔发送
                        if(self.data):
                            self.send_params[id]["interval"][4] = True
                            self.pSendText.set(row, "#1", "停止")
                            self.send_interval(buf, self.send_params[id])
                else: ### 直接发送一次
                    if(self.data):
                        if self.data.channel.type == "UDP":
                            len = self.data.channel.send(buf, tuple(self.send_params[id]["rip"][1]) ) 
                        else:
                            len = self.data.channel.send(buf) 
                        if(not isinstance(len,int)): #发送了零字节， 说明发送出错
                            tkinter.messagebox.showerror( title = "ERROR", message= "通道" + str(self.data.channel.id) + "发送报文出错：" + 
                            self.data.channel.info + self.data.protocol.info +  "\n"+ len.__class__.__name__  + " " + str(len))
                            self.UI.shutdown(self.data)
                            return
                            pass
                        if(self.recv_data[5].get()):
                            if(self.data.channel.type == "UDP"):
                                self.show_msg(buf, len, self.send_params[id]["rip"][1][0] +":" + str(self.send_params[id]["rip"][1][1]))
                            else:
                                self.show_msg(buf, len, "SEND")
                        self.send_count.set(self.send_count.get()+len)
        pass

    def on_treeview_pSendText_contextmenu(self, event):
        column = self.pSendText.identify_column(event.x)
        row = self.pSendText.identify_row(event.y)
        if(row ):
            m = tk.Menu(event.widget)
            m.add_command(label = "删除此报文", command = lambda:self.on_cmd_SendDel_click(row) )
            m.add_command(label = "添加报文", command = self.on_button_SendAdd_click )
            m.add_command(label = "删除全部", command = self.on_button_SendClear_click )
            m.tk_popup(event.x_root , event.y_root)
        else:
            m = tk.Menu(event.widget)
            m.add_command(label = "添加报文", command = self.on_button_SendAdd_click )
            m.add_command(label = "删除全部", command = self.on_button_SendClear_click )
            m.tk_popup(event.x_root , event.y_root)
        pass

    def disable(self):
        """停止响应事件， 停止收发报文 , 仅可以保存报文"""
        #停止发送报文
        if(self.STATE):
            return
        for row in self.pSendText.get_children():
            id = self.pSendText.item(row, "text")
            if(id in self.send_params and self.send_params[id]["interval"][4]):
                self.pSendText.set(row, "#1", "发送")
                self.pSendText.after_cancel(self.send_params[id]["interval"][5])
                self.send_params[id]["interval"][5] = None
        #停止接收到文件
        if(self.recv_data[2].get()):
            self.recv_data[6].close()
            self.recv_data.pop()
        if self.send_file: #正在发送文件需要关闭
            self.send_file.close()
            self.send_file = None
        def set_children_disabled(widget):
            for w in widget.winfo_children():
                if not w.children:
                    if(not w._name.startswith("nodisabled")):
                        try:
                            self.STATE[w] = w.configure("state")[4]
                            w.configure(state="disabled")
                        except Exception as e:
                            pass
                else:
                    set_children_disabled(w)
        set_children_disabled(self)
        pass
    
    def enable(self):
        """恢复响应事件 """
        for s in self.STATE:
            s.configure(state=self.STATE[s])
        self.STATE.clear()
        
    def remove(self):
        #停止发送报文
        self.disable()
            
        #删除窗口
        self.destroy()
    
class Dialog_Send_Param(tkinter.simpledialog.Dialog):
    """发送报文参数设置"""
    def __init__(self, parent, params = None) -> None:
        self.params = params
        self.v = {}
        self.applied = False
        super().__init__(parent, title="设置发送参数：" )
        
        pass
    def body(self, master) -> None:
        # # 以下是默认参数
        # send_params_default_item = {
        #     "hex":["bool", [bool(kwargs["hex"]) if "hex" in kwargs else False, "HEX", ""], "以HEX格式解析发送", None],
        #     "interval":["bool_int", 
        #                 [True  if "interval" in kwargs else False, 
        #                     int(kwargs["interval"]) if "interval" in kwargs else 2000, "", "ms"], 
        #                 "发送间隔", None],
        #     # "str_example":["str", [str(kwargs["str_example"]) if "str_example" in kwargs else "","prefix", "suffix"], "此处显示的配置窗口", "此处显示在发送窗口"],
        #     # "list_example":["list", [list(kwargs["list_example"]) if "list_example" in kwargs else [], "prefix", "suffix", id], "此处显示的配置窗口", "此处显示在发送窗口"],
        #     # "str_int_example":["str_int", ["127.0.0.1",2404,], "远方IP", "此处显示在发送窗口"],
        #     "msg": kwargs["msg"] if "msg" in kwargs else "54 65 73 74 20 4D 65 73 73 61 67 65"
        # }
        r = 0
        for k, v in self.params.items():
            if k == "msg":
                continue
            if v[0] == "bool":
                bv = tk.BooleanVar(master, value= v[1][0], name = k)
                tk.Checkbutton(master, text=v[2], variable=bv).grid(column=0, row= r, sticky="w")
                self.v[k] = [bv]
            elif v[0] == "bool_int":
                bv = tk.BooleanVar(master, value= v[1][0], name = k + "-b")
                iv = tk.IntVar(master, value=v[1][1],  name = k + "-i")
                self.v[k] = [bv, iv]
                tk.Checkbutton(master, text=v[2], variable=bv).grid(column=0, row= r, sticky="w")
                tk.Spinbox(master,width=8, from_=0, to=99999999, increment=1, repeatdelay=30, textvariable= iv).grid(column=1, row= r, sticky="w")
            elif v[0] == "str":
                sv = tk.StringVar(master, value = v[1][0], name = k)
                self.v[k] = [sv]
                tk.Label(master, text= v[2] ).grid(column=0, row= r, sticky="w")
                tk.Entry(master, textvariable=sv ).grid(column=1, row= r, sticky="w")
            elif v[0] == "list":
                sv = tk.StringVar(master, value = "", name = k)
                self.v[k] = [sv]
                tk.Label(master, text= v[2] ).grid(column=0, row= r, sticky="w")
                ttk.Combobox(master,name="lip",values= v[1][0], textvariable= sv).grid(column=1, row= r, sticky="w")
            elif v[0] == "str_int":
                sv = tk.StringVar(master, value = v[1][0], name = k + "-s")
                iv = tk.IntVar(master, value=v[1][1],  name = k + "-i")
                self.v[k] = [sv, iv]
                tk.Label(master, text= v[2] ).grid(column=0, row= r, sticky="w")
                tk.Entry(master, textvariable=sv, width= 16).grid(column=1, row= r, sticky="w")
                tk.Entry(master, textvariable=iv, width= 8).grid(column=2, row= r, sticky="w")
            r += 1
        pass
    def apply(self):
        for k, v in self.params.items():
            if(k in self. v):
                if v[0] == "bool":
                    v[1][0] = self.v[k][0].get()
                elif v[0] == "bool_int":
                    v[1][0],v[1][1] = self.v[k][0].get(), self.v[k][1].get()
                elif v[0] == "str":
                    v[1][0] = self.v[k][0].get()
                elif v[0] == "list":
                    v[1][0] = self.v[k][0].get()
                elif v[0] == "str_int":
                    v[1][0],v[1][1] = self.v[k][0].get(), self.v[k][1].get()
        self.applied  = True
        return self.params

class Dialog_SendText_Param(tkinter.simpledialog.Dialog):
    """发送报文设置"""
    def __init__(self, parent, params = None) -> None:
        self.params = params
        self.applied = False
        super().__init__(parent, title="设置发送报文：" )
        pass
    def body(self, master) -> None:
        self.text = tk.Text(master, width= 60,height= 20)
        self.text.insert ('insert', self.params["msg"])
        self.text.grid(column=0, row= 0)
    def apply(self):
        self.params["msg"] = self.text.get("1.0", "end").rstrip("\n")
        self.applied = True
        return self.params

class Dialog_RecvTextSave_Param(tkinter.simpledialog.Dialog):
    """接收报文保存窗口设置"""
    def __init__(self, parent, params = None) -> None:
        self.params = params
        self.data = None
        super().__init__(parent, title="设置接收报文保存选项：" )
        pass
    def body(self, master) -> None:
        self.v = (  (tk.BooleanVar(master, value= True),"保存序号",),
                    (tk.BooleanVar(master, value= True),"保存时间",), 
                    (tk.BooleanVar(master, value= True),"保存长度",),
                    (tk.BooleanVar(master, value= True),"保存报文",), )
        self.v2 = tk.StringVar(master, value= os.getcwd() + "/recv.log")
        i = 0
        for vv,tt in  self.v:
            w = tk.Checkbutton(master, text= tt,variable= vv )
            w.grid(column=i,row=0)
            i += 1
        tk.Label(master, text= "目标文件：").grid(column = 0, row = 1)
        tk.Entry(master, textvariable= self.v2, state="readonly").grid(column=1, columnspan=2, row = 1)
        tk.Button(master, text= "更改目录", command=self.on_change_dir_click).grid(column=3, row=1)

    def on_change_dir_click(self):
        s = tkinter.filedialog.asksaveasfilename(defaultextension = ".log",
                                                initialfile="recv.log",
                                                initialdir= os.getcwd(),
                                                filetypes=[("log file", ".log"), ("text files",".txt"), ("all files", ".*")])
        if s == '' : return
        self.v2.set(s)
        pass

    def apply(self):
        self.data = (self.v[0][0].get(),self.v[1][0].get(),self.v[2][0].get(),self.v[3][0].get(), self.v2.get())
        return self.data
if __name__ == '__main__':
    pass